{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bb0145c9c1f6ebcc",
   "metadata": {},
   "source": [
    "\n",
    "# Feast Client with RBAC  \n",
    "### Example Using outside of Kubernetes for local testing\n",
    "\n",
    "This notebook will  test Feast authentication outside of Kubernetes for local testing.\n",
    "\n",
    "When running outside of Kubernetes, you need to manually set the service account token in the `LOCAL_K8S_TOKEN` environment variable. The token can be retrieved from a running pod using:  \n",
    "\n",
    "```sh\n",
    "\n",
    "kubectl exec <pod-name> -- cat /var/run/secrets/kubernetes.io/serviceaccount/token\n",
    "\n",
    "```\n",
    "\n",
    "To authenticate Feast externally, set the retrieved token as an environment variable:  \n",
    "\n",
    "```sh\n",
    "\n",
    "export LOCAL_K8S_TOKEN=\"your-service-account-token\"\n",
    "\n",
    "```  \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "160681ba4ab3c2c5",
   "metadata": {},
   "source": [
    "## Test Cases\n",
    "| User Type       | ServiceAccount               | RoleBinding Assigned | Expected Behavior in output                                |\n",
    "|----------------|-----------------------------|----------------------|------------------------------------------------------------|\n",
    "| **Read-Only**  | `feast-user-sa`              | `feast-reader`       | Can **read** from the feature store, but **cannot write**. |\n",
    "| **Unauthorized** | `feast-unauthorized-user-sa` | _None_               | **Access should be denied** in `test.py`.                  |\n",
    "| **Admin**      | `feast-admin-sa`             | `feast-writer`       | Can **read and write** feature store data.                 |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6590c081efb1fe3c",
   "metadata": {},
   "source": [
    "###  Feature Store settings"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84f73e09711bff9f",
   "metadata": {},
   "source": [
    "**The Operator create client feature store ConfigMap** containing the `feature_store.yaml `settings. We can retrieve it and port froward to local as we are testing locally."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "456fb4df46f32380",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:56:42.630556Z",
     "start_time": "2025-03-14T14:56:42.455312Z"
    }
   },
   "outputs": [],
   "source": [
    "!kubectl get configmap feast-sample-kubernetes-auth-client -n feast -o jsonpath='{.data.feature_store\\.yaml}' > client/feature_repo/feature_store.yaml"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e322f5c",
   "metadata": {},
   "source": [
    "Update the `client/feature_repo/feature_store.yaml` to look like below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f50b802",
   "metadata": {
    "vscode": {
     "languageId": "xml"
    }
   },
   "outputs": [],
   "source": [
    "project: feast_rbac\n",
    "provider: local\n",
    "offline_store:\n",
    "    host: localhost\n",
    "    type: remote\n",
    "    port: 8081\n",
    "online_store:\n",
    "    path: http://localhost:8082\n",
    "    type: remote\n",
    "registry:\n",
    "    path: localhost:8083\n",
    "    registry_type: remote\n",
    "auth:\n",
    "    type: kubernetes\n",
    "entity_key_serialization_version: 3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae61f4dca31f3466",
   "metadata": {},
   "source": [
    "### The function below is executed to support the preparation of client testing."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28636825ae8f676d",
   "metadata": {},
   "source": [
    "Run Port Forwarding for All Services for local testing "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c014248190863e8a",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:57:03.137990Z",
     "start_time": "2025-03-14T14:57:03.096768Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Port forwarding feast-sample-kubernetes-auth-offline -> localhost:8081\n",
      "Port forwarding feast-sample-kubernetes-auth-online -> localhost:8082\n",
      "Port forwarding feast-sample-kubernetes-auth-registry -> localhost:8083\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Forwarding from 127.0.0.1:8081 -> 8815\n",
      "Forwarding from [::1]:8081 -> 8815\n",
      "Forwarding from 127.0.0.1:8082 -> 6566\n",
      "Forwarding from [::1]:8082 -> 6566\n",
      "Forwarding from 127.0.0.1:8083 -> 6570\n",
      "Forwarding from [::1]:8083 -> 6570\n"
     ]
    }
   ],
   "source": [
    "import subprocess\n",
    "\n",
    "# Define services and their local ports\n",
    "services = {\n",
    "    \"offline_store\": (\"feast-sample-kubernetes-auth-offline\", 8081),\n",
    "    \"online_store\": (\"feast-sample-kubernetes-auth-online\", 8082),\n",
    "    \"registry\": (\"feast-sample-kubernetes-auth-registry\", 8083),\n",
    "}\n",
    "\n",
    "# Start port-forwarding for each service\n",
    "port_forward_processes = {}\n",
    "for name, (service, local_port) in services.items():\n",
    "    cmd = f\"kubectl port-forward svc/{service} -n feast {local_port}:80\"\n",
    "    process = subprocess.Popen(cmd, shell=True)\n",
    "    port_forward_processes[name] = process\n",
    "    print(f\"Port forwarding {service} -> localhost:{local_port}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0eccef6379f442c",
   "metadata": {},
   "source": [
    "Function to retrieve a Kubernetes service account token and set it as an environment variable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "70bdbcd7b3fe44",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:57:42.336072Z",
     "start_time": "2025-03-14T14:57:42.332818Z"
    }
   },
   "outputs": [],
   "source": [
    "import subprocess\n",
    "import os\n",
    "\n",
    "def get_k8s_token(service_account):\n",
    "    namespace = \"feast\"\n",
    "\n",
    "    if not service_account:\n",
    "        raise ValueError(\"Service account name is required.\")\n",
    "\n",
    "    result = subprocess.run(\n",
    "        [\"kubectl\", \"create\", \"token\", service_account, \"-n\", namespace],\n",
    "        capture_output=True, text=True, check=True\n",
    "    )\n",
    "\n",
    "    token = result.stdout.strip()\n",
    "\n",
    "    if not token:\n",
    "        return None  # Silently return None if token retrieval fails\n",
    "\n",
    "    os.environ[\"LOCAL_K8S_TOKEN\"] = token\n",
    "    return \"Token Retrieved: ***** (hidden for security)\"\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c9e27ec4ed8ca2c",
   "metadata": {},
   "source": [
    "**Generating training data**. The following test functions were copied from the `test_workflow.py` template but we added `try` blocks to print only \n",
    "the relevant error messages, since we expect to receive errors from the permission enforcement modules."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "934963c5f6b18930",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:58:09.731552Z",
     "start_time": "2025-03-14T14:58:06.599552Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "import os\n",
      "\n",
      "from feast import FeatureStore\n",
      "from feast.data_source import PushMode\n",
      "from datetime import datetime\n",
      "import pandas as pd\n",
      "\n",
      "# Initialize Feature Store\n",
      "repo_path = os.getenv(\"FEAST_REPO_PATH\", \".\")\n",
      "store = FeatureStore(repo_path=repo_path)\n",
      "\n",
      "def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring: bool):\n",
      "    \"\"\"Fetch historical features for training or batch scoring.\"\"\"\n",
      "    try:\n",
      "        entity_df = pd.DataFrame.from_dict(\n",
      "            {\n",
      "                \"driver_id\": [1001, 1002, 1003],\n",
      "                \"event_timestamp\": [\n",
      "                    datetime(2021, 4, 12, 10, 59, 42),\n",
      "                    datetime(2021, 4, 12, 8, 12, 10),\n",
      "                    datetime(2021, 4, 12, 16, 40, 26),\n",
      "                ],\n",
      "                \"label_driver_reported_satisfaction\": [1, 5, 3],\n",
      "                \"val_to_add\": [1, 2, 3],\n",
      "                \"val_to_add_2\": [10, 20, 30],\n",
      "            }\n",
      "        )\n",
      "        if for_batch_scoring:\n",
      "            entity_df[\"event_timestamp\"] = pd.to_datetime(\"now\", utc=True)\n",
      "\n",
      "        training_df = store.get_historical_features(\n",
      "            entity_df=entity_df,\n",
      "            features=[\n",
      "                \"driver_hourly_stats:conv_rate\",\n",
      "                \"driver_hourly_stats:acc_rate\",\n",
      "                \"driver_hourly_stats:avg_daily_trips\",\n",
      "                \"transformed_conv_rate:conv_rate_plus_val1\",\n",
      "                \"transformed_conv_rate:conv_rate_plus_val2\",\n",
      "            ],\n",
      "        ).to_df()\n",
      "        print(f\"Successfully fetched {'batch scoring' if for_batch_scoring else 'training'} historical features:\\n\", training_df.head())\n",
      "\n",
      "    except PermissionError:\n",
      "        print(\"\\n*** PERMISSION DENIED *** Cannot fetch historical features.\")\n",
      "    except Exception as e:\n",
      "        print(f\"Unexpected error while fetching historical features: {e}\")\n",
      "\n",
      "def fetch_online_features(store: FeatureStore, source: str = \"\"):\n",
      "    \"\"\"Fetch online features from the feature store.\"\"\"\n",
      "    try:\n",
      "        entity_rows = [\n",
      "            {\n",
      "                \"driver_id\": 1001,\n",
      "                \"val_to_add\": 1000,\n",
      "                \"val_to_add_2\": 2000,\n",
      "            },\n",
      "            {\n",
      "                \"driver_id\": 1002,\n",
      "                \"val_to_add\": 1001,\n",
      "                \"val_to_add_2\": 2002,\n",
      "            },\n",
      "        ]\n",
      "        if source == \"feature_service\":\n",
      "            features_to_fetch = store.get_feature_service(\"driver_activity_v1\")\n",
      "        elif source == \"push\":\n",
      "            features_to_fetch = store.get_feature_service(\"driver_activity_v3\")\n",
      "        else:\n",
      "            features_to_fetch = [\n",
      "                \"driver_hourly_stats:acc_rate\",\n",
      "                \"transformed_conv_rate:conv_rate_plus_val1\",\n",
      "                \"transformed_conv_rate:conv_rate_plus_val2\",\n",
      "            ]\n",
      "\n",
      "        returned_features = store.get_online_features(\n",
      "            features=features_to_fetch,\n",
      "            entity_rows=entity_rows,\n",
      "        ).to_dict()\n",
      "\n",
      "        print(f\"Successfully fetched online features {'via feature service' if source else 'directly'}:\\n\")\n",
      "        for key, value in sorted(returned_features.items()):\n",
      "            print(f\"{key} : {value}\")\n",
      "\n",
      "    except PermissionError:\n",
      "        print(\"\\n*** PERMISSION DENIED *** Cannot fetch online features.\")\n",
      "    except Exception as e:\n",
      "        print(f\"Unexpected error while fetching online features: {e}\")\n",
      "\n",
      "def check_permissions():\n",
      "    \"\"\"Check user role, test various Feast operations.\"\"\"\n",
      "    feature_views = []\n",
      "\n",
      "    # Step 1: List feature views\n",
      "    print(\"\\n--- List feature views ---\")\n",
      "    try:\n",
      "        feature_views = store.list_feature_views()\n",
      "        if not feature_views:\n",
      "            print(\"No feature views found. You might not have access or they haven't been created.\")\n",
      "        else:\n",
      "            print(f\"Successfully listed {len(feature_views)} feature views:\")\n",
      "            for fv in feature_views:\n",
      "                print(f\"  - {fv.name}\")\n",
      "    except PermissionError:\n",
      "        print(\"\\n*** PERMISSION DENIED *** Cannot list feature views.\")\n",
      "    except Exception as e:\n",
      "        print(f\"Unexpected error listing feature views: {e}\")\n",
      "\n",
      "    # Step 2: Fetch Historical Features\n",
      "    print(\"\\n--- Fetching Historical Features for Training ---\")\n",
      "    fetch_historical_features_entity_df(store, for_batch_scoring=False)\n",
      "\n",
      "    print(\"\\n--- Fetching Historical Features for Batch Scoring ---\")\n",
      "    fetch_historical_features_entity_df(store, for_batch_scoring=True)\n",
      "\n",
      "    # Step 3: Apply Feature Store\n",
      "    print(\"\\n--- Write to Feature Store ---\")\n",
      "    try:\n",
      "        store.apply(feature_views)\n",
      "        print(\"User has write access to the feature store.\")\n",
      "    except PermissionError:\n",
      "        print(\"\\n*** PERMISSION DENIED *** User lacks permission to modify the feature store.\")\n",
      "    except Exception as e:\n",
      "        print(f\"Unexpected error testing write access: {e}\")\n",
      "\n",
      "    # Step 4: Fetch Online Features\n",
      "    print(\"\\n--- Fetching Online Features ---\")\n",
      "    fetch_online_features(store)\n",
      "\n",
      "    print(\"\\n--- Fetching Online Features via Feature Service ---\")\n",
      "    fetch_online_features(store, source=\"feature_service\")\n",
      "\n",
      "    print(\"\\n--- Fetching Online Features via Push Source ---\")\n",
      "    fetch_online_features(store, source=\"push\")\n",
      "\n",
      "    print(\"\\n--- Performing Push Source ---\")\n",
      "    # Step 5: Simulate Event Push (Streaming Ingestion)\n",
      "    try:\n",
      "        event_df = pd.DataFrame.from_dict(\n",
      "            {\n",
      "                \"driver_id\": [1001],\n",
      "                \"event_timestamp\": [datetime.now()],\n",
      "                \"created\": [datetime.now()],\n",
      "                \"conv_rate\": [1.0],\n",
      "                \"acc_rate\": [1.0],\n",
      "                \"avg_daily_trips\": [1000],\n",
      "            }\n",
      "        )\n",
      "        store.push(\"driver_stats_push_source\", event_df, to=PushMode.ONLINE_AND_OFFLINE)\n",
      "        print(\"Successfully pushed a test event.\")\n",
      "    except PermissionError:\n",
      "        print(\"\\n*** PERMISSION DENIED *** Cannot push event (no write access).\")\n",
      "    except Exception as e:\n",
      "        print(f\"Unexpected error while pushing event: {e}\")\n",
      "\n",
      "if __name__ == \"__main__\":\n",
      "    check_permissions()\n"
     ]
    }
   ],
   "source": [
    "!cat client/feature_repo/test.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "7c20ea331dc5a09b",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:58:15.658631Z",
     "start_time": "2025-03-14T14:58:15.653740Z"
    }
   },
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "# Set the FEAST_REPO_PATH before importing check_permissions\n",
    "os.environ[\"FEAST_REPO_PATH\"] = \"client/feature_repo\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84e3f83699b8d83",
   "metadata": {},
   "source": [
    "### Test Read-Only Feast User \n",
    "**Step 1: Set the Token**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "f1fe8baa02d27d38",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:58:27.147260Z",
     "start_time": "2025-03-14T14:58:24.035744Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Token Retrieved: ***** (hidden for security)'"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "get_k8s_token(\"feast-user-sa\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "140c909fa8bcc6ab",
   "metadata": {},
   "source": [
    "**Step 2: Test permission functions to validate permission on fetching online, offline or perform write operation**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "14b7ad38368db767",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:58:57.328137Z",
     "start_time": "2025-03-14T14:58:54.623074Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "--- List feature views ---\n",
      "Handling connection for 8083\n",
      "Successfully listed 2 feature views:\n",
      "  - driver_hourly_stats_fresh\n",
      "  - driver_hourly_stats\n",
      "\n",
      "--- Fetching Historical Features for Training ---\n",
      "Handling connection for 8081\n",
      "Successfully fetched training historical features:\n",
      "    driver_id           event_timestamp  label_driver_reported_satisfaction  \\\n",
      "0       1001 2021-04-12 10:59:42+00:00                                   1   \n",
      "1       1002 2021-04-12 08:12:10+00:00                                   5   \n",
      "2       1003 2021-04-12 16:40:26+00:00                                   3   \n",
      "\n",
      "   val_to_add  val_to_add_2  conv_rate  acc_rate  avg_daily_trips  \\\n",
      "0           1            10   0.287198  0.415895              540   \n",
      "1           2            20   0.694777  0.622882              912   \n",
      "2           3            30   0.107433  0.887705              491   \n",
      "\n",
      "   conv_rate_plus_val1  conv_rate_plus_val2  \n",
      "0             1.287198            10.287198  \n",
      "1             2.694777            20.694777  \n",
      "2             3.107433            30.107433  \n",
      "\n",
      "--- Fetching Historical Features for Batch Scoring ---\n",
      "Handling connection for 8081\n",
      "Successfully fetched batch scoring historical features:\n",
      "    driver_id                  event_timestamp  \\\n",
      "0       1002 2025-05-29 14:55:09.809015+00:00   \n",
      "1       1001 2025-05-29 14:55:09.809015+00:00   \n",
      "2       1003 2025-05-29 14:55:09.809015+00:00   \n",
      "\n",
      "   label_driver_reported_satisfaction  val_to_add  val_to_add_2  conv_rate  \\\n",
      "0                                   5           2            20   0.576347   \n",
      "1                                   1           1            10   0.881758   \n",
      "2                                   3           3            30   0.069664   \n",
      "\n",
      "   acc_rate  avg_daily_trips  conv_rate_plus_val1  conv_rate_plus_val2  \n",
      "0  0.555302              227             2.576347            20.576347  \n",
      "1  0.833210              951             1.881758            10.881758  \n",
      "2  0.557969              435             3.069664            30.069664  \n",
      "\n",
      "--- Write to Feature Store ---\n",
      "\n",
      "*** PERMISSION DENIED *** User lacks permission to modify the feature store.\n",
      "\n",
      "--- Fetching Online Features ---\n",
      "Handling connection for 8082\n",
      "Successfully fetched online features directly:\n",
      "\n",
      "acc_rate : [0.1861499696969986, 0.7390778660774231]\n",
      "conv_rate_plus_val1 : [1000.6494747400284, 1001.6300525069237]\n",
      "conv_rate_plus_val2 : [2000.6494747400284, 2002.6300525069237]\n",
      "driver_id : [1001, 1002]\n",
      "\n",
      "--- Fetching Online Features via Feature Service ---\n",
      "Handling connection for 8082\n",
      "Successfully fetched online features via feature service:\n",
      "\n",
      "conv_rate : [0.6494747400283813, 0.6300525069236755]\n",
      "conv_rate_plus_val1 : [1000.6494747400284, 1001.6300525069237]\n",
      "conv_rate_plus_val2 : [2000.6494747400284, 2002.6300525069237]\n",
      "driver_id : [1001, 1002]\n",
      "\n",
      "--- Fetching Online Features via Push Source ---\n",
      "Handling connection for 8082\n",
      "Successfully fetched online features via feature service:\n",
      "\n",
      "acc_rate : [None, None]\n",
      "avg_daily_trips : [None, None]\n",
      "conv_rate : [None, None]\n",
      "conv_rate_plus_val1 : [None, None]\n",
      "conv_rate_plus_val2 : [None, None]\n",
      "driver_id : [1001, 1002]\n",
      "\n",
      "--- Performing Push Source ---\n",
      "Unexpected error while pushing event: \n"
     ]
    }
   ],
   "source": [
    "from client.feature_repo.test import  check_permissions\n",
    "\n",
    "# Call the function\n",
    "#Run test.py script from pod to test RBAC for client-readonly-user.\n",
    "# verify the logs for write operation will show below message \n",
    "# --- Write to Feature Store ---\n",
    "#*** PERMISSION DENIED *** User lacks permission to modify the feature store.\n",
    "\n",
    "check_permissions()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83c358cf",
   "metadata": {},
   "source": [
    "**Step 3: Test permission functions to validate permission on fetching online feature views**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ab365dd",
   "metadata": {},
   "source": [
    "Required:\n",
    " - The Bearer token exported above as `LOCAL_K8S_TOKEN`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "176fdde4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Handling connection for 8082\n",
      "{\"metadata\":{\"feature_names\":[\"driver_id\",\"acc_rate\",\"conv_rate\"]},\"results\":[{\"values\":[1001,1002],\"statuses\":[\"PRESENT\",\"PRESENT\"],\"event_timestamps\":[\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\"]},{\"values\":[0.1861499696969986,0.7390778660774231],\"statuses\":[\"PRESENT\",\"PRESENT\"],\"event_timestamps\":[\"2025-05-20T11:00:00Z\",\"2025-05-20T11:00:00Z\"]},{\"values\":[0.6494747400283813,0.6300525069236755],\"statuses\":[\"PRESENT\",\"PRESENT\"],\"event_timestamps\":[\"2025-05-20T11:00:00Z\",\"2025-05-20T11:00:00Z\"]}]}"
     ]
    }
   ],
   "source": [
    "# Run Curl command to test the RBAC for client-readonly-user.\n",
    "!curl -X POST http://localhost:8082/get-online-features -H \"Content-Type: application/json\" -H \"Authorization: Bearer $LOCAL_K8S_TOKEN\" -d '{\"features\": [\"driver_hourly_stats:conv_rate\",\"driver_hourly_stats:acc_rate\"], \"entities\":{\"driver_id\": [1001, 1002]}}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5e63a172da6d6d7",
   "metadata": {},
   "source": [
    "### Test Unauthorized Feast User "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1011729",
   "metadata": {},
   "source": [
    "**Step 1: Test permission functions to validate unauthorized user cant access any objects**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "a7b3a6578fcf5c3c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:59:12.042904Z",
     "start_time": "2025-03-14T14:59:11.959795Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Token Retrieved: ***** (hidden for security)'"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Retrieve and store the token\n",
    "get_k8s_token(\"feast-unauthorized-user-sa\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "7aea5658325ab008",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:59:14.629414Z",
     "start_time": "2025-03-14T14:59:14.375404Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "--- List feature views ---\n",
      "No feature views found. You might not have access or they haven't been created.\n",
      "\n",
      "--- Fetching Historical Features for Training ---\n",
      "\n",
      "*** PERMISSION DENIED *** Cannot fetch historical features.\n",
      "\n",
      "--- Fetching Historical Features for Batch Scoring ---\n",
      "\n",
      "*** PERMISSION DENIED *** Cannot fetch historical features.\n",
      "\n",
      "--- Write to Feature Store ---\n",
      "\n",
      "*** PERMISSION DENIED *** User lacks permission to modify the feature store.\n",
      "\n",
      "--- Fetching Online Features ---\n",
      "\n",
      "*** PERMISSION DENIED *** Cannot fetch online features.\n",
      "\n",
      "--- Fetching Online Features via Feature Service ---\n",
      "\n",
      "*** PERMISSION DENIED *** Cannot fetch online features.\n",
      "\n",
      "--- Fetching Online Features via Push Source ---\n",
      "\n",
      "*** PERMISSION DENIED *** Cannot fetch online features.\n",
      "\n",
      "--- Performing Push Source ---\n",
      "Unexpected error while pushing event: Unable to find push source 'driver_stats_push_source'.\n"
     ]
    }
   ],
   "source": [
    "check_permissions()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ecea09ae",
   "metadata": {},
   "source": [
    "**Step 2: Run API request for Unauthorized User, Unauthorized user should not be able to even view the objects.**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1464cef0",
   "metadata": {},
   "source": [
    "Required:\n",
    " - The Bearer token exported above as `LOCAL_K8S_TOKEN`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "c15e084a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Handling connection for 8082\n",
      "\"{\\\"module\\\": \\\"feast.errors\\\", \\\"class\\\": \\\"FeastPermissionError\\\", \\\"message\\\": \\\"Permission error:\\\\nPermission feast_user_permission denied execution of ['READ_ONLINE'] to FeatureView:driver_hourly_stats: Requires roles ['feast-reader'],Permission feast_admin_permission denied execution of ['READ_ONLINE'] to FeatureView:driver_hourly_stats: Requires roles ['feast-writer']\\\"}\""
     ]
    }
   ],
   "source": [
    "# Run Curl command to test the RBAC for client-readonly-user.\n",
    "!curl -X POST http://localhost:8082/get-online-features -H \"Content-Type: application/json\" -H \"Authorization: Bearer $LOCAL_K8S_TOKEN\" -d '{\"features\": [\"driver_hourly_stats:conv_rate\",\"driver_hourly_stats:acc_rate\"], \"entities\":{\"driver_id\": [1001, 1002]}}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb78ced7c37ceb4c",
   "metadata": {},
   "source": [
    "## Test Admin Feast User"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60f11ebf",
   "metadata": {},
   "source": [
    "**Step 1: Test permissions function to validate Admin user can access all objects**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "4f10aae116825619",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:59:23.745351Z",
     "start_time": "2025-03-14T14:59:23.647728Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Token Retrieved: ***** (hidden for security)'"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Retrieve and store the token\n",
    "get_k8s_token(\"feast-admin-sa\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "7a6133f052b9cfe1",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:59:28.583546Z",
     "start_time": "2025-03-14T14:59:26.745747Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "--- List feature views ---\n",
      "Successfully listed 2 feature views:\n",
      "  - driver_hourly_stats_fresh\n",
      "  - driver_hourly_stats\n",
      "\n",
      "--- Fetching Historical Features for Training ---\n",
      "Handling connection for 8081\n",
      "Successfully fetched training historical features:\n",
      "    driver_id           event_timestamp  label_driver_reported_satisfaction  \\\n",
      "0       1001 2021-04-12 10:59:42+00:00                                   1   \n",
      "1       1002 2021-04-12 08:12:10+00:00                                   5   \n",
      "2       1003 2021-04-12 16:40:26+00:00                                   3   \n",
      "\n",
      "   val_to_add  val_to_add_2  conv_rate  acc_rate  avg_daily_trips  \\\n",
      "0           1            10   0.287198  0.415895              540   \n",
      "1           2            20   0.694777  0.622882              912   \n",
      "2           3            30   0.107433  0.887705              491   \n",
      "\n",
      "   conv_rate_plus_val1  conv_rate_plus_val2  \n",
      "0             1.287198            10.287198  \n",
      "1             2.694777            20.694777  \n",
      "2             3.107433            30.107433  \n",
      "\n",
      "--- Fetching Historical Features for Batch Scoring ---\n",
      "Handling connection for 8081\n",
      "Successfully fetched batch scoring historical features:\n",
      "    driver_id                  event_timestamp  \\\n",
      "0       1002 2025-05-29 14:56:06.863642+00:00   \n",
      "1       1001 2025-05-29 14:56:06.863642+00:00   \n",
      "2       1003 2025-05-29 14:56:06.863642+00:00   \n",
      "\n",
      "   label_driver_reported_satisfaction  val_to_add  val_to_add_2  conv_rate  \\\n",
      "0                                   5           2            20   0.576347   \n",
      "1                                   1           1            10   0.881758   \n",
      "2                                   3           3            30   0.069664   \n",
      "\n",
      "   acc_rate  avg_daily_trips  conv_rate_plus_val1  conv_rate_plus_val2  \n",
      "0  0.555302              227             2.576347            20.576347  \n",
      "1  0.833210              951             1.881758            10.881758  \n",
      "2  0.557969              435             3.069664            30.069664  \n",
      "\n",
      "--- Write to Feature Store ---\n",
      "User has write access to the feature store.\n",
      "\n",
      "--- Fetching Online Features ---\n",
      "Handling connection for 8082\n",
      "Successfully fetched online features directly:\n",
      "\n",
      "acc_rate : [0.1861499696969986, 0.7390778660774231]\n",
      "conv_rate_plus_val1 : [1000.6494747400284, 1001.6300525069237]\n",
      "conv_rate_plus_val2 : [2000.6494747400284, 2002.6300525069237]\n",
      "driver_id : [1001, 1002]\n",
      "\n",
      "--- Fetching Online Features via Feature Service ---\n",
      "Handling connection for 8082\n",
      "Successfully fetched online features via feature service:\n",
      "\n",
      "conv_rate : [0.6494747400283813, 0.6300525069236755]\n",
      "conv_rate_plus_val1 : [1000.6494747400284, 1001.6300525069237]\n",
      "conv_rate_plus_val2 : [2000.6494747400284, 2002.6300525069237]\n",
      "driver_id : [1001, 1002]\n",
      "\n",
      "--- Fetching Online Features via Push Source ---\n",
      "Handling connection for 8082\n",
      "Successfully fetched online features via feature service:\n",
      "\n",
      "acc_rate : [None, None]\n",
      "avg_daily_trips : [None, None]\n",
      "conv_rate : [None, None]\n",
      "conv_rate_plus_val1 : [None, None]\n",
      "conv_rate_plus_val2 : [None, None]\n",
      "driver_id : [1001, 1002]\n",
      "\n",
      "--- Performing Push Source ---\n",
      "Unexpected error while pushing event: \n"
     ]
    }
   ],
   "source": [
    "check_permissions()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70ef3b39",
   "metadata": {},
   "source": [
    "**Step 2: Run API request for Admin User, Admin user should be able to perform all operations on the objects.**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8033f26c",
   "metadata": {},
   "source": [
    "Required:\n",
    " - The Bearer token exported above as `LOCAL_K8S_TOKEN`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "9577cc2b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Handling connection for 8082\n",
      "{\"metadata\":{\"feature_names\":[\"driver_id\",\"acc_rate\",\"conv_rate\"]},\"results\":[{\"values\":[1001,1002],\"statuses\":[\"PRESENT\",\"PRESENT\"],\"event_timestamps\":[\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\"]},{\"values\":[0.1861499696969986,0.7390778660774231],\"statuses\":[\"PRESENT\",\"PRESENT\"],\"event_timestamps\":[\"2025-05-20T11:00:00Z\",\"2025-05-20T11:00:00Z\"]},{\"values\":[0.6494747400283813,0.6300525069236755],\"statuses\":[\"PRESENT\",\"PRESENT\"],\"event_timestamps\":[\"2025-05-20T11:00:00Z\",\"2025-05-20T11:00:00Z\"]}]}"
     ]
    }
   ],
   "source": [
    "# Run Curl command to test the RBAC for client-readonly-user.\n",
    "!curl -X POST http://localhost:8082/get-online-features -H \"Content-Type: application/json\" -H \"Authorization: Bearer $LOCAL_K8S_TOKEN\" -d '{\"features\": [\"driver_hourly_stats:conv_rate\",\"driver_hourly_stats:acc_rate\"], \"entities\":{\"driver_id\": [1001, 1002]}}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e451c30649630b2f",
   "metadata": {},
   "source": [
    " **Note:**\n",
    "**Currently, remote materialization not available in Feast when using the Remote Client**\n",
    "**Workaround: Consider using running it from pod like**\n",
    "  \n",
    " `kubectl exec deploy/feast-sample-kubernetes-auth -itc online -- bash -c 'feast materialize-incremental $(date -u +\"%Y-%m-%dT%H:%M:%S\")`\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e88442b1bae2b327",
   "metadata": {},
   "source": [
    "Terminate the process"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "2984d62766da122a",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-14T14:59:48.327362Z",
     "start_time": "2025-03-14T14:59:48.324482Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Stopped port forwarding for offline_store\n",
      "Stopped port forwarding for online_store\n",
      "Stopped port forwarding for registry\n"
     ]
    }
   ],
   "source": [
    "for name, process in port_forward_processes.items():\n",
    "    process.terminate()\n",
    "    print(f\"Stopped port forwarding for {name}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "38c54e92643e0bda",
   "metadata": {},
   "source": [
    "[Next: Uninstall the Operator and all Feast objects](./04-uninstall.ipynb)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
